package com.zeng.ssm.service.support.idempotent;


import cn.hutool.core.lang.UUID;
import com.zeng.ssm.api.support.idempotent.DistributedLockService;
import com.zeng.ssm.api.support.idempotent.KeyIdempotent;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.EnvironmentAccessor;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;


/**
 * 基于业务主键实现的幂等性切面
 * <p>
 * 利用Redis分布式锁/Memcached
 * <p>
 * 该aop包裹事务aop
 *
 * @author zengwanlin
 * @date 2021-10-28T17:36
 */
@Component
@Aspect
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
public class KeyIdempotentAspect implements InitializingBean {

    private final Logger logger = LoggerFactory.getLogger(KeyIdempotentAspect.class);

    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();

    @Autowired
    private DistributedLockService distributedLockService;

    @Around("@annotation(keyIdempotent)")
    public Object aroundCut(ProceedingJoinPoint joinPoint, KeyIdempotent keyIdempotent) throws Throwable {
        Assert.hasText(keyIdempotent.key(), "key表达式不能为空");

        // 获取参数
        Object[] args = joinPoint.getArgs();

        // 获取参数名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] parameterNames = methodSignature.getParameterNames();

        // 获取EvaluationContext
        StandardEvaluationContext context = getEvaluationContext(parameterNames, args);

        // 解析Redis key
        Expression expression = EXPRESSION_PARSER.parseExpression(keyIdempotent.key(), ParserContext.TEMPLATE_EXPRESSION);
        String value = expression.getValue(context, String.class);
        if (!StringUtils.hasText(value)) {
            throw new IllegalArgumentException("解析key值为空，请检查表达式是否正确");
        }
        if (logger.isDebugEnabled()) {
            logger.debug("KEY幂等性切面拦截，获取key：{}", value);
        }

        // 分布式锁加锁
        String lockKey = keyIdempotent.prefix() + keyIdempotent.busiType().getBusiCode() + value;
        String uniqueValue = UUID.fastUUID().toString();
        boolean suc = distributedLockService.lock(lockKey, uniqueValue, keyIdempotent.interval());
        if (!suc) {
            // 加锁失败，重复请求
            throw new IllegalStateException("幂等性验证不通过，重复请求，请稍后刷新查看");
        }
        // 加锁成功
        try {
            // 执行目标方法
            return joinPoint.proceed();
        } finally {
            // 方法执行完毕，解锁
            distributedLockService.unlock(lockKey, uniqueValue);
        }

    }

    /**
     * 构造StandardEvaluationContext对象
     *
     * @param parameterNames 参数名称arr
     * @param args           参数值arr
     * @return
     */
    private StandardEvaluationContext getEvaluationContext(String[] parameterNames, Object[] args) {
        StandardEvaluationContext context = new StandardEvaluationContext();
        if (ObjectUtils.isEmpty(parameterNames)) {
            return context;
        }
        context.addPropertyAccessor(new MapAccessor());
        context.addPropertyAccessor(new EnvironmentAccessor());
        for (int i = 0; i < parameterNames.length; i++) {
            // 设置参数
            context.setVariable(parameterNames[i], args[i]);
        }
        return context;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(distributedLockService, "distributedLockService属性不能为空");
    }

}
